/*
 * Copyright 2018 stfalcon.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.stfalcon.imageviewer.viewer.dialog;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.Image;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.PageSlider;
import ohos.agp.components.PageSliderProvider;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.window.dialog.CommonDialog;
import ohos.app.Context;
import ohos.multimodalinput.event.KeyEvent;
import ohos.multimodalinput.event.TouchEvent;

import com.github.chrisbanes.photoview.PhotoView;
import com.stfalcon.imageviewer.ResourceTable;
import com.stfalcon.imageviewer.common.extensions.ViewExt;
import com.stfalcon.imageviewer.listeners.OnDismissListener;
import com.stfalcon.imageviewer.loader.ImageLoader;
import com.stfalcon.imageviewer.viewer.builder.BuilderData;

import java.util.ArrayList;
import java.util.List;

/**
 * 图片查看器弹窗
 *
 * @param <T> 数据类型
 * @since 2021-07-20
 */
public class ImageViewerDialog<T> {
    private static final long ANIMATION_DURATION = 200L;
    private static final long TRANSITION_DURATION = 350L;
    private static final int TOUCH_SLOP = 22;
    private static final int DEFAULT_STATUS_BAR = -1;
    private static final int POS_0 = 0;
    private static final int POS_1 = 1;
    private static final int POS_2 = 2;
    private static final int POS_3 = 3;
    private static final int POS_4 = 4;
    private int statusBarHeight = DEFAULT_STATUS_BAR;
    private final Context context;
    private final BuilderData<T> builderData;
    private CommonDialog dialog;
    private final boolean isZoomingAllowed;
    private StackLayout rootContainer;
    private StackLayout backgroundView;
    private StackLayout dismissContainer;
    private Image transitionImageView;
    private PageSlider imagesPager;
    private Component overlayView;
    private final List<T> images = new ArrayList<>();
    private final ImageLoader<T> imageLoader;
    private int startPosition;
    private PageSliderProvider imagesAdapter;
    private final boolean isSwipeToDismissAllowed;
    private float startX;
    private float startY;
    private boolean isTracking;
    private Image externalTransitionImageView;
    private int externalImageWidth;
    private int externalImageHeight;
    private int externalImageLeft;
    private int externalImageTop;
    private int targetWidth;
    private int targetHeight;
    private int targetLeft;
    private int targetTop;
    private float currentTranslationY = POS_0;
    private final int backgroundColor;
    private final int imagePageMargin;
    private final int[] containerPadding;
    private final OnDismissListener dismissListener;
    private boolean isShouldStatusBarHide;
    private boolean isTranslateYDismiss = false;
    private boolean isOverlayVisible = true;
    private final ViewExt.PageSliderChangedAdapter pageChangedListener = new ViewExt.PageSliderChangedAdapter() {
        @Override
        public void onPageChosen(int position) {
            onImageChanged(position);
        }
    };

    /**
     * 默认构造
     *
     * @param context 上下文
     * @param builderData 构造数据
     */
    public ImageViewerDialog(Context context, BuilderData<T> builderData) {
        this.context = context;
        this.builderData = builderData;
        this.images.addAll(builderData.getImages());
        this.imageLoader = builderData.getImageLoader();
        this.startPosition = builderData.getStartPosition();
        this.externalTransitionImageView = builderData.getTransitionView();
        this.isSwipeToDismissAllowed = builderData.isSwipeToDismissAllowed();
        this.isZoomingAllowed = builderData.isZoomingAllowed();
        this.backgroundColor = builderData.getBackgroundColor();
        this.imagePageMargin = builderData.getImageMarginPixels();
        this.containerPadding = builderData.getContainerPaddingPixels();
        this.dismissListener = builderData.getOnDismissListener();
        this.isShouldStatusBarHide = builderData.isShouldStatusBarHide();
        this.overlayView = builderData.getOverlayView();
        prepare();
    }

    private void onImageChanged(int position) {
        if (builderData.getImageChangeListener() != null) {
            builderData.getImageChangeListener().onImageChange(position);
        }
        imageLoader.loadImage(transitionImageView, images.get(position));
    }

    private void prepare() {
        rootContainer = (StackLayout) LayoutScatter.getInstance(context)
            .parse(ResourceTable.Layout_view_image_viewer, null, false);
        backgroundView = (StackLayout) rootContainer.findComponentById(ResourceTable.Id_backgroundView);
        setBackgroundColor(backgroundColor);
        dismissContainer = (StackLayout) rootContainer.findComponentById(ResourceTable.Id_dismissContainer);
        transitionImageView = (Image) rootContainer.findComponentById(ResourceTable.Id_transitionImageView);
        imagesPager = (PageSlider) rootContainer.findComponentById(ResourceTable.Id_imagesPager);
        imagesPager.setPageMargin(imagePageMargin);
        imagesPager.setMarginLeft(containerPadding[POS_0]);
        imagesPager.setMarginTop(containerPadding[POS_1]);
        imagesPager.setMarginRight(containerPadding[POS_2]);
        imagesPager.setMarginBottom(containerPadding[POS_3]);
        imagesAdapter = new PagerAdapter();
        imagesPager.setProvider(imagesAdapter);
        imagesPager.addPageChangedListener(pageChangedListener);
        imageLoader.loadImage(transitionImageView, images.get(startPosition));
        imagesPager.setCurrentPage(startPosition);
        prepareTransitionLayout();
        dialog = new CommonDialog(context);
        dialog.setContentCustomComponent(rootContainer);
        dialog.setTransparent(true);
        dialog.setDestroyedListener(() -> {
            if (dismissListener != null) {
                dismissListener.onDismiss();
            }
        });
        dialog.siteKeyboardCallback((iDialog, keyEvent) -> {
            if (keyEvent.getKeyCode() == KeyEvent.KEY_BACK && keyEvent.getKeyDownDuration() == 0) {
                if (isSwipeToDismissAllowed) {
                    close();
                } else {
                    dismiss();
                }
            }
            return true;
        });
        if (isShouldStatusBarHide) {
            dialog.getWindow().setStatusBarVisibility(Component.INVISIBLE);
        } else {
            dialog.getWindow().setStatusBarVisibility(Component.VISIBLE);
        }
        setOverlayView(this.overlayView);
    }

    private void setBackgroundColor(int color) {
        ShapeElement bg = new ShapeElement();
        bg.setRgbColor(RgbColor.fromArgbInt(color));
        backgroundView.setBackground(bg);
    }

    private void prepareTransitionLayout() {
        if (externalTransitionImageView == null) {
            isTranslateYDismiss = true;
            startPosition = getCurrentPosition();
            rootContainer.setBindStateChangedListener(new Component.BindStateChangedListener() {
                @Override
                public void onComponentBoundToWindow(Component component) {
                    externalImageWidth = imagesPager.getWidth();
                    targetWidth = imagesPager.getWidth();
                    externalImageHeight = imagesPager.getHeight();
                    targetHeight = imagesPager.getHeight();
                    externalImageLeft = imagesPager.getLeft();
                    targetLeft = imagesPager.getLeft();
                    externalImageTop = imagesPager.getTop();
                    targetTop = imagesPager.getTop();
                    transitionImageView.setMarginLeft(externalImageLeft);
                    transitionImageView.setMarginTop(externalImageTop);
                }

                @Override
                public void onComponentUnboundFromWindow(Component component) {
                }
            });
        } else {
            externalImageWidth = externalTransitionImageView.getWidth();
            externalImageHeight = externalTransitionImageView.getHeight();
            rootContainer.setBindStateChangedListener(new Component.BindStateChangedListener() {
                @Override
                public void onComponentBoundToWindow(Component component) {
                    targetWidth = imagesPager.getWidth();
                    targetHeight = imagesPager.getHeight();
                    targetLeft = imagesPager.getLeft();
                    targetTop = imagesPager.getTop();
                    refreshExternalImageLocation();
                    transitionImageView.setMarginLeft(externalImageLeft);
                    transitionImageView.setMarginTop(externalImageTop);
                }

                @Override
                public void onComponentUnboundFromWindow(Component component) {
                }
            });
        }
        transitionImageView.setWidth(externalImageWidth);
        transitionImageView.setHeight(externalImageHeight);
    }

    private void refreshExternalImageLocation() {
        int[] externalLocation = externalTransitionImageView.getLocationOnScreen();
        if (statusBarHeight == DEFAULT_STATUS_BAR) {
            int[] rootLocation = rootContainer.getLocationOnScreen();
            statusBarHeight = rootLocation[1];
        }
        externalImageLeft = externalLocation[0];
        externalImageTop = externalLocation[1] - statusBarHeight;
    }

    private float getEventDistance(TouchEvent ev) {
        float dx = ev.getPointerPosition(0).getX() - startX;
        float dy = ev.getPointerPosition(0).getY() - startY;
        return (float) Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * 数据适配器
     *
     * @since 2021-07-20
     */
    private class PagerAdapter extends PageSliderProvider {
        @Override
        public int getCount() {
            return images.size();
        }

        @Override
        public Object createPageInContainer(ComponentContainer componentContainer, int position) {
            PhotoView photoView = new PhotoView(context);
            imageLoader.loadImage(photoView, images.get(position));
            photoView.setLayoutConfig(new ComponentContainer.LayoutConfig(
                ComponentContainer.LayoutConfig.MATCH_PARENT,
                ComponentContainer.LayoutConfig.MATCH_PARENT));
            Component.TouchEventListener photoViewTouchEventListener = photoView.getTouchEventListener();
            photoView.setZoomable(isZoomingAllowed);
            if (!isZoomingAllowed && isSwipeToDismissAllowed) {
                aciton1(photoView);
            } else {
                action2(photoView, photoViewTouchEventListener);
            }
            photoView.setPageSlider(imagesPager);
            componentContainer.addComponent(photoView);
            return photoView;
        }

        private void action2(PhotoView photoView, Component.TouchEventListener photoViewTouchEventListener) {
            photoView.setTouchEventListener((component, event) -> {
                if (photoView.getPhotoScale() == 1) {
                    if (isSwipeToDismissAllowed) {
                        switch (event.getAction()) {
                            case TouchEvent.PRIMARY_POINT_DOWN:
                                startX = event.getPointerScreenPosition(0).getX();
                                startY = event.getPointerScreenPosition(0).getY();
                                return photoViewTouchEventListener.onTouchEvent(component, event);
                            case TouchEvent.POINT_MOVE:
                                if (getEventDistance(event) > TOUCH_SLOP) {
                                    float currentX = event.getPointerScreenPosition(0).getX();
                                    float dx = currentX - startX;
                                    float currentY = event.getPointerScreenPosition(0).getY();
                                    float dy = currentY - startY;
                                    if (Math.abs(dy) > Math.abs(dx)) {
                                        isTracking = true;
                                        handleVerticalDragEvent(dy);
                                        return true;
                                    }
                                }
                                return photoViewTouchEventListener.onTouchEvent(component, event);
                            case TouchEvent.PRIMARY_POINT_UP:
                            case TouchEvent.CANCEL:
                                if (isTracking) {
                                    isTracking = false;
                                    handleVerticalReleaseEvent();
                                    return true;
                                }
                                return photoViewTouchEventListener.onTouchEvent(component, event);
                            default:
                                return photoViewTouchEventListener.onTouchEvent(component, event);
                        }
                    }
                }
                return photoViewTouchEventListener.onTouchEvent(component, event);
            });
        }

        private void aciton1(PhotoView photoView) {
            photoView.setTouchEventListener((component, event) -> {
                switch (event.getAction()) {
                    case TouchEvent.PRIMARY_POINT_DOWN:
                        startX = event.getPointerScreenPosition(0).getX();
                        startY = event.getPointerScreenPosition(0).getY();
                        break;
                    case TouchEvent.POINT_MOVE:
                        if (getEventDistance(event) > TOUCH_SLOP) {
                            float currentX = event.getPointerScreenPosition(0).getX();
                            float dx = currentX - startX;
                            float currentY = event.getPointerScreenPosition(0).getY();
                            float dy = currentY - startY;
                            if (Math.abs(dy) > Math.abs(dx)) {
                                isTracking = true;
                                handleVerticalDragEvent(dy);
                            }
                        }
                        break;
                    case TouchEvent.PRIMARY_POINT_UP:
                    case TouchEvent.CANCEL:
                        if (isTracking) {
                            isTracking = false;
                            handleVerticalReleaseEvent();
                        }
                        break;
                    default:
                        break;
                }
                return true;
            });
        }

        @Override
        public void destroyPageFromContainer(ComponentContainer componentContainer, int position, Object obj) {
            componentContainer.removeComponent((Component) obj);
        }

        @Override
        public boolean isPageMatchToObject(Component component, Object o) {
            return o == component;
        }
    }

    private void handleVerticalDragEvent(float dy) {
        currentTranslationY = dy;
        dismissContainer.setTranslationY(currentTranslationY);
        final int translationLimit = dismissContainer.getHeight() / 4;
        float alpha = 1.0f - 1.0f / translationLimit / 4f * Math.abs(currentTranslationY);
        backgroundView.setAlpha(alpha);
        if (overlayView != null) {
            overlayView.setAlpha(alpha);
        }
    }

    private void handleVerticalReleaseEvent() {
        final int translationLimit = dismissContainer.getHeight() / 4;
        final float translationY = dismissContainer.getTranslationY();
        if (Math.abs(translationY) > translationLimit) {
            endTransition();
        } else {
            AnimatorValue resetAnimator = new AnimatorValue();
            resetAnimator.setDuration(ANIMATION_DURATION);
            resetAnimator.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
            resetAnimator.setValueUpdateListener((animatorValue, v) -> {
                float currentTranslationY = translationY - v * translationY;
                dismissContainer.setTranslationY(currentTranslationY);
                float alpha = 1.0f - 1.0f / translationLimit / 4f * Math.abs(translationY);
                backgroundView.setAlpha(alpha);
                if (overlayView != null) {
                    overlayView.setAlpha(alpha);
                }
            });
            resetAnimator.setStateChangedListener(new ViewExt.AnimatorListenerAdapter() {
                @Override
                public void onEnd(Animator animator) {
                    backgroundView.setAlpha(1);
                    if (overlayView != null) {
                        overlayView.setAlpha(1);
                    }
                }
            });
            resetAnimator.start();
        }
    }

    private void setOverlayView(Component view) {
        if (overlayView != null) {
            this.overlayView = view;
            this.overlayView.setClickedListener(component -> {
                if (isOverlayVisible) {
                    overlayView.createAnimatorProperty().alpha(0).start();
                } else {
                    overlayView.createAnimatorProperty().alpha(1).start();
                }
                isOverlayVisible = !isOverlayVisible;
            });
            rootContainer.addComponent(overlayView);
            isOverlayVisible = true;
        } else {
            isOverlayVisible = false;
        }
    }

    /**
     * 打开弹窗
     *
     * @param animate 是否开启动画
     */
    public void show(boolean animate) {
        dialog.show();
        if (animate) {
            startTransition();
        }
    }

    private void startTransition() {
        AnimatorValue transition = new AnimatorValue();
        transition.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
        transition.setDuration(TRANSITION_DURATION);
        transition.setValueUpdateListener((animatorValue, v) -> {
            int width = (int) (externalImageWidth + (targetWidth - externalImageWidth) * v);
            int height = (int) (externalImageHeight + (targetHeight - externalImageHeight) * v);
            int left = (int) (externalImageLeft + (targetLeft - externalImageLeft) * v);
            int top = (int) (externalImageTop + (targetTop - externalImageTop) * v);
            transitionImageView.setWidth(width);
            transitionImageView.setHeight(height);
            transitionImageView.setMarginLeft(left);
            transitionImageView.setMarginTop(top);
            backgroundView.setAlpha(v);
            dismissContainer.setAlpha(v);
        });
        transition.setStateChangedListener(new ViewExt.AnimatorListenerAdapter() {
            @Override
            public void onEnd(Animator animator) {
                if (externalTransitionImageView != null) {
                    ViewExt.makeInvisible(externalTransitionImageView);
                }
                ViewExt.makeInvisible(transitionImageView);
                ViewExt.makeVisible(imagesPager);
            }
        });

        transition.start();
    }

    private void endTransition() {
        if (isTranslateYDismiss) {
            AnimatorValue transition = new AnimatorValue();
            transition.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
            transition.setDuration(TRANSITION_DURATION);
            boolean isUp = currentTranslationY >= 0;
            transition.setValueUpdateListener((animatorValue, v) -> {
                float translationY = isUp ? currentTranslationY + dismissContainer.getHeight() * v
                    : currentTranslationY - dismissContainer.getHeight() * v;
                dismissContainer.setTranslationY(translationY);
                backgroundView.setAlpha(1 - v);
                dismissContainer.setAlpha(1 - v);
            });
            transition.setStateChangedListener(new ViewExt.AnimatorListenerAdapter() {
                @Override
                public void onStart(Animator animator) {
                    ViewExt.makeInvisible(imagesPager);
                    ViewExt.makeVisible(transitionImageView);
                }

                @Override
                public void onEnd(Animator animator) {
                    if (externalTransitionImageView != null) {
                        ViewExt.makeVisible(externalTransitionImageView);
                    }
                    dialog.destroy();
                }
            });
            transition.start();
        } else {
            AnimatorValue transition = new AnimatorValue();
            transition.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE);
            transition.setDuration(TRANSITION_DURATION);
            transition.setValueUpdateListener((animatorValue, v) -> {
                int width = (int) (targetWidth + (externalImageWidth - targetWidth) * v);
                int height = (int) (targetHeight + (externalImageHeight - targetHeight) * v);
                int left = (int) (targetLeft + (externalImageLeft - targetLeft) * v);
                int top = (int) (targetTop + (externalImageTop - targetTop) * v);
                transitionImageView.setWidth(width);
                transitionImageView.setHeight(height);
                transitionImageView.setMarginLeft(left);
                transitionImageView.setMarginTop(top);
                float translationY = currentTranslationY + (0 - currentTranslationY) * v;
                dismissContainer.setTranslationY(translationY);
                backgroundView.setAlpha(1 - v);
                dismissContainer.setAlpha(1 - v);
            });
            transition.setStateChangedListener(new ViewExt.AnimatorListenerAdapter() {
                @Override
                public void onStart(Animator animator) {
                    ViewExt.makeInvisible(imagesPager);
                    ViewExt.makeVisible(transitionImageView);
                }

                @Override
                public void onEnd(Animator animator) {
                    if (externalTransitionImageView != null) {
                        ViewExt.makeVisible(externalTransitionImageView);
                    }
                    dialog.destroy();
                }
            });
            transition.start();
        }
    }

    /**
     * 关闭弹窗，有动画
     */
    public void close() {
        endTransition();
    }

    /**
     * 关闭弹窗，无动画
     */
    public void dismiss() {
        dialog.destroy();
        if (externalTransitionImageView != null) {
            ViewExt.makeVisible(externalTransitionImageView);
        }
    }

    /**
     * 更新图片资源
     *
     * @param images 图片资源
     */
    public void updateImages(List<T> images) {
        final int currentPosition = getCurrentPosition();
        int position = currentPosition;
        if (currentPosition == imagesAdapter.getCount() - 1) {
            position = currentPosition - 1;
        }
        if (position < 0 && imagesAdapter.getCount() > 0) {
            position = 0;
        }
        this.images.clear();
        this.images.addAll(images);
        imagesAdapter = new PagerAdapter();
        imagesPager.setProvider(imagesAdapter);
        imagesPager.setCurrentPage(position, false);
    }

    /**
     * 当前展示的图片索引
     *
     * @return 当前图片索引
     */
    public int getCurrentPosition() {
        return imagesPager.getCurrentPage();
    }

    /**
     * 设置当前图片索引
     *
     * @param position 图片索引
     * @return 当前显示的图片的索引
     */
    public int setCurrentPosition(int position) {
        imagesPager.setCurrentPage(position);
        return position;
    }

    /**
     * 更新需要执行过渡动画的image
     *
     * @param imageView 执行过渡动画的image
     */
    public void updateTransitionImage(Image imageView) {
        if (imageView != null) {
            isTranslateYDismiss = false;
            if (externalTransitionImageView != null) {
                ViewExt.makeVisible(externalTransitionImageView);
            }
            ViewExt.makeInvisible(imageView);
            externalTransitionImageView = imageView;
            startPosition = getCurrentPosition();
            imageLoader.loadImage(transitionImageView, images.get(startPosition));
            externalImageWidth = externalTransitionImageView.getWidth();
            externalImageHeight = externalTransitionImageView.getHeight();

            refreshExternalImageLocation();
        } else {
            isTranslateYDismiss = true;
            startPosition = getCurrentPosition();
        }
    }
}
